/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */
// cl_parse.c  -- parse a message received from the server

#include "client.h"

char *svc_strings[256] = {
    "svc_bad",

    "svc_nop",
    "svc_gamestate",
    "svc_configstring",
    "svc_baseline",
    "svc_serverCommand",
    "svc_download",
    "svc_snapshot",
    "svc_EOF",
    "svc_extension",
    "svc_voip",
};

void SHOWNET(msg_t *msg, char *s) {
    if (cl_shownet->integer >= 2) {
        Com_Printf("%3i:%s\n", msg->readcount - 1, s);
    }
}


/*
=========================================================================

MESSAGE PARSING

=========================================================================
 */

/*
==================
CL_DeltaEntity

Parses deltas from the given base and adds the resulting entity
to the current frame
==================
 */
void CL_DeltaEntity(msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old,
        qboolean unchanged) {
    entityState_t *state;

    // save the parsed entity state into the big circular buffer so
    // it can be used as the source for a later delta
    state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES - 1)];

    if (unchanged) {
        *state = *old;
    } else {
        MSG_ReadDeltaEntity(msg, old, state, newnum);
    }

    if (state->number == (MAX_GENTITIES - 1)) {
        return; // entity was delta removed
    }
    cl.parseEntitiesNum++;
    frame->numEntities++;
}

/*
==================
CL_ParsePacketEntities

==================
 */
void CL_ParsePacketEntities(msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) {
    int newnum;
    entityState_t *oldstate;
    int oldindex, oldnum;

    newframe->parseEntitiesNum = cl.parseEntitiesNum;
    newframe->numEntities = 0;

    // delta from the entities present in oldframe
    oldindex = 0;
    oldstate = NULL;
    if (!oldframe) {
        oldnum = 99999;
    } else {
        if (oldindex >= oldframe->numEntities) {
            oldnum = 99999;
        } else {
            oldstate = &cl.parseEntities[
                    (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES - 1)];
            oldnum = oldstate->number;
        }
    }

    while (1) {
        // read the entity index number
        newnum = MSG_ReadBits(msg, GENTITYNUM_BITS);

        if (newnum == (MAX_GENTITIES - 1)) {
            break;
        }

        if (msg->readcount > msg->cursize) {
            Com_Error(ERR_DROP, "CL_ParsePacketEntities: end of message");
        }

        while (oldnum < newnum) {
            // one or more entities from the old packet are unchanged
            if (cl_shownet->integer == 3) {
                Com_Printf("%3i:  unchanged: %i\n", msg->readcount, oldnum);
            }
            CL_DeltaEntity(msg, newframe, oldnum, oldstate, qtrue);

            oldindex++;

            if (oldindex >= oldframe->numEntities) {
                oldnum = 99999;
            } else {
                oldstate = &cl.parseEntities[
                        (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES - 1)];
                oldnum = oldstate->number;
            }
        }
        if (oldnum == newnum) {
            // delta from previous state
            if (cl_shownet->integer == 3) {
                Com_Printf("%3i:  delta: %i\n", msg->readcount, newnum);
            }
            CL_DeltaEntity(msg, newframe, newnum, oldstate, qfalse);

            oldindex++;

            if (oldindex >= oldframe->numEntities) {
                oldnum = 99999;
            } else {
                oldstate = &cl.parseEntities[
                        (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES - 1)];
                oldnum = oldstate->number;
            }
            continue;
        }

        if (oldnum > newnum) {
            // delta from baseline
            if (cl_shownet->integer == 3) {
                Com_Printf("%3i:  baseline: %i\n", msg->readcount, newnum);
            }
            CL_DeltaEntity(msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse);
            continue;
        }

    }

    // any remaining entities in the old frame are copied over
    while (oldnum != 99999) {
        // one or more entities from the old packet are unchanged
        if (cl_shownet->integer == 3) {
            Com_Printf("%3i:  unchanged: %i\n", msg->readcount, oldnum);
        }
        CL_DeltaEntity(msg, newframe, oldnum, oldstate, qtrue);

        oldindex++;

        if (oldindex >= oldframe->numEntities) {
            oldnum = 99999;
        } else {
            oldstate = &cl.parseEntities[
                    (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES - 1)];
            oldnum = oldstate->number;
        }
    }
}

/*
================
CL_ParseSnapshot

If the snapshot is parsed properly, it will be copied to
cl.snap and saved in cl.snapshots[].  If the snapshot is invalid
for any reason, no changes to the state will be made at all.
================
 */
void CL_ParseSnapshot(msg_t *msg) {
    int len;
    clSnapshot_t *old;
    clSnapshot_t newSnap;
    int deltaNum;
    int oldMessageNum;
    int i, packetNum;

    // get the reliable sequence acknowledge number
    // NOTE: now sent with all server to client messages
    //clc.reliableAcknowledge = MSG_ReadLong( msg );

    // read in the new snapshot to a temporary buffer
    // we will only copy to cl.snap if it is valid
    Com_Memset(&newSnap, 0, sizeof (newSnap));

    // we will have read any new server commands in this
    // message before we got to svc_snapshot
    newSnap.serverCommandNum = clc.serverCommandSequence;

    newSnap.serverTime = MSG_ReadLong(msg);

    // if we were just unpaused, we can only *now* really let the
    // change come into effect or the client hangs.
    cl_paused->modified = 0;

    newSnap.messageNum = clc.serverMessageSequence;

    deltaNum = MSG_ReadByte(msg);
    if (!deltaNum) {
        newSnap.deltaNum = -1;
    } else {
        newSnap.deltaNum = newSnap.messageNum - deltaNum;
    }
    newSnap.snapFlags = MSG_ReadByte(msg);

    // If the frame is delta compressed from data that we
    // no longer have available, we must suck up the rest of
    // the frame, but not use it, then ask for a non-compressed
    // message
    if (newSnap.deltaNum <= 0) {
        newSnap.valid = qtrue; // uncompressed frame
        old = NULL;
        clc.demowaiting = qfalse; // we can start recording now
    } else {
        old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
        if (!old->valid) {
            // should never happen
            Com_Printf("Delta from invalid frame (not supposed to happen!).\n");
        } else if (old->messageNum != newSnap.deltaNum) {
            // The frame that the server did the delta from
            // is too old, so we can't reconstruct it properly.
            Com_Printf("Delta frame too old.\n");
        } else if (cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - 128) {
            Com_Printf("Delta parseEntitiesNum too old.\n");
        } else {
            newSnap.valid = qtrue; // valid delta parse
        }
    }

    // read areamask
    len = MSG_ReadByte(msg);

    if (len > sizeof (newSnap.areamask)) {
        Com_Error(ERR_DROP, "CL_ParseSnapshot: Invalid size %d for areamask.", len);
        return;
    }

    MSG_ReadData(msg, &newSnap.areamask, len);

    // read playerinfo
    SHOWNET(msg, "playerstate");
    if (old) {
        MSG_ReadDeltaPlayerstate(msg, &old->ps, &newSnap.ps);
    } else {
        MSG_ReadDeltaPlayerstate(msg, NULL, &newSnap.ps);
    }

    // read packet entities
    SHOWNET(msg, "packet entities");
    CL_ParsePacketEntities(msg, old, &newSnap);

    // if not valid, dump the entire thing now that it has
    // been properly read
    if (!newSnap.valid) {
        return;
    }

    // clear the valid flags of any snapshots between the last
    // received and this one, so if there was a dropped packet
    // it won't look like something valid to delta from next
    // time we wrap around in the buffer
    oldMessageNum = cl.snap.messageNum + 1;

    if (newSnap.messageNum - oldMessageNum >= PACKET_BACKUP) {
        oldMessageNum = newSnap.messageNum - (PACKET_BACKUP - 1);
    }
    for (; oldMessageNum < newSnap.messageNum; oldMessageNum++) {
        cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
    }

    // copy to the current good spot
    cl.snap = newSnap;
    cl.snap.ping = 999;
    // calculate ping time
    for (i = 0; i < PACKET_BACKUP; i++) {
        packetNum = (clc.netchan.outgoingSequence - 1 - i) & PACKET_MASK;
        if (cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime) {
            cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
            break;
        }
    }
    // save the frame off in the backup array for later delta comparisons
    cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;

    if (cl_shownet->integer == 3) {
        Com_Printf("   snapshot:%i  delta:%i  ping:%i\n", cl.snap.messageNum,
                cl.snap.deltaNum, cl.snap.ping);
    }

    cl.newSnapshots = qtrue;
}


//=====================================================================

int cl_connectedToPureServer;
int cl_connectedToCheatServer;

#ifdef USE_VOIP
int cl_connectedToVoipServer;
#endif

/*
==================
CL_SystemInfoChanged

The systeminfo configstring has been changed, so parse
new information out of it.  This will happen at every
gamestate, and possibly during gameplay.
==================
 */
void CL_SystemInfoChanged(void) {
    char *systemInfo;
    const char *s, *t;
    char key[BIG_INFO_KEY];
    char value[BIG_INFO_VALUE];
    qboolean gameSet;

    systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
    // NOTE TTimo:
    // when the serverId changes, any further messages we send to the server will use this new serverId
    // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
    // in some cases, outdated cp commands might get sent with this news serverId
    cl.serverId = atoi(Info_ValueForKey(systemInfo, "sv_serverid"));

    // don't set any vars when playing a demo
    if (clc.demoplaying) {
        return;
    }

#ifdef USE_VOIP
    // in the future, (val) will be a protocol version string, so only
    //  accept explicitly 1, not generally non-zero.
    s = Info_ValueForKey(systemInfo, "sv_voip");
    if (Cvar_VariableValue("g_gametype") == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
        cl_connectedToVoipServer = qfalse;
    else
        cl_connectedToVoipServer = (atoi(s) == 1);

#endif

    s = Info_ValueForKey(systemInfo, "sv_cheats");
    cl_connectedToCheatServer = atoi(s);
    if (!cl_connectedToCheatServer) {
        Cvar_SetCheatState();
    }

    // check pure server string
    s = Info_ValueForKey(systemInfo, "sv_paks");
    t = Info_ValueForKey(systemInfo, "sv_pakNames");
    FS_PureServerSetLoadedPaks(s, t);

    s = Info_ValueForKey(systemInfo, "sv_referencedPaks");
    t = Info_ValueForKey(systemInfo, "sv_referencedPakNames");
    FS_PureServerSetReferencedPaks(s, t);

    gameSet = qfalse;
    // scan through all the variables in the systeminfo and locally set cvars to match
    s = systemInfo;
    while (s) {
        int cvar_flags;

        Info_NextPair(&s, key, value);
        if (!key[0]) {
            break;
        }

        // ehw!
        if (!Q_stricmp(key, "fs_game")) {
            if (FS_CheckDirTraversal(value)) {
                Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value);
                continue;
            }

            gameSet = qtrue;
        }

        if ((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT)
            Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM);
        else {
            // If this cvar may not be modified by a server discard the value.
            if (!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED))) {
                Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
                continue;
            }

            Cvar_Set(key, value);
        }
    }
    // if game folder should not be set and it is set at the client side
    if (!gameSet && *Cvar_VariableString("fs_game")) {
        Cvar_Set("fs_game", "");
    }
    cl_connectedToPureServer = Cvar_VariableValue("sv_pure");
}

/*
==================
CL_ParseServerInfo
==================
 */
static void CL_ParseServerInfo(void) {
    const char *serverInfo;

    serverInfo = cl.gameState.stringData
            + cl.gameState.stringOffsets[ CS_SERVERINFO ];

    clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo,
            "sv_allowDownload"));
    Q_strncpyz(clc.sv_dlURL,
            Info_ValueForKey(serverInfo, "sv_dlURL"),
            sizeof (clc.sv_dlURL));
}

/*
==================
CL_ParseGamestate
==================
 */
void CL_ParseGamestate(msg_t *msg) {
    int i;
    entityState_t *es;
    int newnum;
    entityState_t nullstate;
    int cmd;
    char *s;

    Con_Close();

    clc.connectPacketCount = 0;

    // wipe local client state
    CL_ClearState();

    // a gamestate always marks a server command sequence
    clc.serverCommandSequence = MSG_ReadLong(msg);

    // parse all the configstrings and baselines
    cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings
    while (1) {
        cmd = MSG_ReadByte(msg);

        if (cmd == svc_EOF) {
            break;
        }

        if (cmd == svc_configstring) {
            int len;

            i = MSG_ReadShort(msg);
            if (i < 0 || i >= MAX_CONFIGSTRINGS) {
                Com_Error(ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
            }
            s = MSG_ReadBigString(msg);
            len = strlen(s);

            if (len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS) {
                Com_Error(ERR_DROP, "MAX_GAMESTATE_CHARS exceeded");
            }

            // append it to the gameState string buffer
            cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
            Com_Memcpy(cl.gameState.stringData + cl.gameState.dataCount, s, len + 1);
            cl.gameState.dataCount += len + 1;
        } else if (cmd == svc_baseline) {
            newnum = MSG_ReadBits(msg, GENTITYNUM_BITS);
            if (newnum < 0 || newnum >= MAX_GENTITIES) {
                Com_Error(ERR_DROP, "Baseline number out of range: %i", newnum);
            }
            Com_Memset(&nullstate, 0, sizeof (nullstate));
            es = &cl.entityBaselines[ newnum ];
            MSG_ReadDeltaEntity(msg, &nullstate, es, newnum);
        } else {
            Com_Error(ERR_DROP, "CL_ParseGamestate: bad command byte");
        }
    }

    clc.clientNum = MSG_ReadLong(msg);
    // read the checksum feed
    clc.checksumFeed = MSG_ReadLong(msg);

    // parse useful values out of CS_SERVERINFO
    CL_ParseServerInfo();

    // parse serverId and other cvars
    CL_SystemInfoChanged();

    // stop recording now so the demo won't have an unnecessary level load at the end.
    if (cl_autoRecordDemo->integer && clc.demorecording)
        CL_StopRecord_f();

    // reinitialize the filesystem if the game directory has changed
    FS_ConditionalRestart(clc.checksumFeed);

    // This used to call CL_StartHunkUsers, but now we enter the download state before loading the
    // cgame
    CL_InitDownloads();

    // make sure the game starts
    Cvar_Set("cl_paused", "0");
}


//=====================================================================

/*
=====================
CL_ParseDownload

A download message has been received from the server
=====================
 */
void CL_ParseDownload(msg_t *msg) {
    int size;
    unsigned char data[MAX_MSGLEN];
    int block;

    if (!*clc.downloadTempName) {
        Com_Printf("Server sending download, but no download was requested\n");
        CL_AddReliableCommand("stopdl", qfalse);
        return;
    }

    // read the data
    block = MSG_ReadShort(msg);

    if (!block) {
        // block zero is special, contains file size
        clc.downloadSize = MSG_ReadLong(msg);

        Cvar_SetValue("cl_downloadSize", clc.downloadSize);

        if (clc.downloadSize < 0) {
            Com_Error(ERR_DROP, "%s", MSG_ReadString(msg));
            return;
        }
    }

    size = MSG_ReadShort(msg);
    if (size < 0 || size > sizeof (data)) {
        Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size);
        return;
    }

    MSG_ReadData(msg, data, size);

    if (clc.downloadBlock != block) {
        Com_DPrintf("CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block);
        return;
    }

    // open the file if not opened yet
    if (!clc.download) {
        clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName);

        if (!clc.download) {
            Com_Printf("Could not create %s\n", clc.downloadTempName);
            CL_AddReliableCommand("stopdl", qfalse);
            CL_NextDownload();
            return;
        }
    }

    if (size)
        FS_Write(data, size, clc.download);

    CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse);
    clc.downloadBlock++;

    clc.downloadCount += size;

    // So UI gets access to it
    Cvar_SetValue("cl_downloadCount", clc.downloadCount);

    if (!size) { // A zero length block means EOF
        if (clc.download) {
            FS_FCloseFile(clc.download);
            clc.download = 0;

            // rename the file
            FS_SV_Rename(clc.downloadTempName, clc.downloadName);
        }

        // send intentions now
        // We need this because without it, we would hold the last nextdl and then start
        // loading right away.  If we take a while to load, the server is happily trying
        // to send us that last block over and over.
        // Write it twice to help make sure we acknowledge the download
        CL_WritePacket();
        CL_WritePacket();

        // get another file if needed
        CL_NextDownload();
    }
}

#ifdef USE_VOIP

static
qboolean CL_ShouldIgnoreVoipSender(int sender) {
    if (!cl_voip->integer)
        return qtrue; // VoIP is disabled.
    else if ((sender == clc.clientNum) && (!clc.demoplaying))
        return qtrue; // ignore own voice (unless playing back a demo).
    else if (clc.voipMuteAll)
        return qtrue; // all channels are muted with extreme prejudice.
    else if (clc.voipIgnore[sender])
        return qtrue; // just ignoring this guy.
    else if (clc.voipGain[sender] == 0.0f)
        return qtrue; // too quiet to play.

    return qfalse;
}

/*
=====================
CL_ParseVoip

A VoIP message has been received from the server
=====================
 */
static
void CL_ParseVoip(msg_t *msg) {
    static short decoded[4096]; // !!! FIXME: don't hardcode.

    const int sender = MSG_ReadShort(msg);
    const int generation = MSG_ReadByte(msg);
    const int sequence = MSG_ReadLong(msg);
    const int frames = MSG_ReadByte(msg);
    const int packetsize = MSG_ReadShort(msg);
    char encoded[1024];
    int seqdiff = sequence - clc.voipIncomingSequence[sender];
    int written = 0;
    int i;

    Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);

    if (sender < 0)
        return; // short/invalid packet, bail.
    else if (generation < 0)
        return; // short/invalid packet, bail.
    else if (sequence < 0)
        return; // short/invalid packet, bail.
    else if (frames < 0)
        return; // short/invalid packet, bail.
    else if (packetsize < 0)
        return; // short/invalid packet, bail.

    if (packetsize > sizeof (encoded)) { // overlarge packet?
        int bytesleft = packetsize;
        while (bytesleft) {
            int br = bytesleft;
            if (br > sizeof (encoded))
                br = sizeof (encoded);
            MSG_ReadData(msg, encoded, br);
            bytesleft -= br;
        }
        return; // overlarge packet, bail.
    }

    if (!clc.speexInitialized) {
        MSG_ReadData(msg, encoded, packetsize); // skip payload.
        return; // can't handle VoIP without libspeex!
    } else if (sender >= MAX_CLIENTS) {
        MSG_ReadData(msg, encoded, packetsize); // skip payload.
        return; // bogus sender.
    } else if (CL_ShouldIgnoreVoipSender(sender)) {
        MSG_ReadData(msg, encoded, packetsize); // skip payload.
        return; // Channel is muted, bail.
    }

    // !!! FIXME: make sure data is narrowband? Does decoder handle this?

    Com_DPrintf("VoIP: packet accepted!\n");

    // This is a new "generation" ... a new recording started, reset the bits.
    if (generation != clc.voipIncomingGeneration[sender]) {
        Com_DPrintf("VoIP: new generation %d!\n", generation);
        speex_bits_reset(&clc.speexDecoderBits[sender]);
        clc.voipIncomingGeneration[sender] = generation;
        seqdiff = 0;
    } else if (seqdiff < 0) { // we're ahead of the sequence?!
        // This shouldn't happen unless the packet is corrupted or something.
        Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
                sequence, clc.voipIncomingSequence[sender]);
        // reset the bits just in case.
        speex_bits_reset(&clc.speexDecoderBits[sender]);
        seqdiff = 0;
    } else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
        // just start over.
        Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
                seqdiff, sender);
        speex_bits_reset(&clc.speexDecoderBits[sender]);
        seqdiff = 0;
    }

    if (seqdiff != 0) {
        Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
                seqdiff, sender);
        // tell speex that we're missing frames...
        for (i = 0; i < seqdiff; i++) {
            assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
            speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
            written += clc.speexFrameSize;
        }
    }

    for (i = 0; i < frames; i++) {
        char encoded[256];
        const int len = MSG_ReadByte(msg);
        if (len < 0) {
            Com_DPrintf("VoIP: Short packet!\n");
            break;
        }
        MSG_ReadData(msg, encoded, len);

        // shouldn't happen, but just in case...
        if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
            Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
                    written * 2, written, i);
            S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
                    (const byte *) decoded, clc.voipGain[sender]);
            written = 0;
        }

        speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
        speex_decode_int(clc.speexDecoder[sender],
                &clc.speexDecoderBits[sender], decoded + written);

#if 0
        static FILE *encio = NULL;
        if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
        if (encio != NULL) {
            fwrite(encoded, len, 1, encio);
            fflush(encio);
        }
        static FILE *decio = NULL;
        if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
        if (decio != NULL) {
            fwrite(decoded + written, clc.speexFrameSize * 2, 1, decio);
            fflush(decio);
        }
#endif

        written += clc.speexFrameSize;
    }

    Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
            written * 2, written, i);

    if (written > 0) {
        S_RawSamples(sender + 1, written, clc.speexSampleRate, 2, 1,
                (const byte *) decoded, clc.voipGain[sender]);
    }

    clc.voipIncomingSequence[sender] = sequence + frames;
}
#endif

/*
=====================
CL_ParseCommandString

Command strings are just saved off until cgame asks for them
when it transitions a snapshot
=====================
 */
void CL_ParseCommandString(msg_t *msg) {
    char *s;
    int seq;
    int index;

    seq = MSG_ReadLong(msg);
    s = MSG_ReadString(msg);

    // see if we have already executed stored it off
    if (clc.serverCommandSequence >= seq) {
        return;
    }
    clc.serverCommandSequence = seq;

    index = seq & (MAX_RELIABLE_COMMANDS - 1);
    Q_strncpyz(clc.serverCommands[ index ], s, sizeof ( clc.serverCommands[ index ]));
}

/*
=====================
CL_ParseServerMessage
=====================
 */
void CL_ParseServerMessage(msg_t *msg) {
    int cmd;

    if (cl_shownet->integer == 1) {
        Com_Printf("%i ", msg->cursize);
    } else if (cl_shownet->integer >= 2) {
        Com_Printf("------------------\n");
    }

    MSG_Bitstream(msg);

    // get the reliable sequence acknowledge number
    clc.reliableAcknowledge = MSG_ReadLong(msg);
    //
    if (clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS) {
        clc.reliableAcknowledge = clc.reliableSequence;
    }

    //
    // parse the message
    //
    while (1) {
        if (msg->readcount > msg->cursize) {
            Com_Error(ERR_DROP, "CL_ParseServerMessage: read past end of server message");
            break;
        }

        cmd = MSG_ReadByte(msg);

        // See if this is an extension command after the EOF, which means we
        //  got data that a legacy client should ignore.
        if ((cmd == svc_EOF) && (MSG_LookaheadByte(msg) == svc_extension)) {
            SHOWNET(msg, "EXTENSION");
            MSG_ReadByte(msg); // throw the svc_extension byte away.
            cmd = MSG_ReadByte(msg); // something legacy clients can't do!
            // sometimes you get a svc_extension at end of stream...dangling
            //  bits in the huffman decoder giving a bogus value?
            if (cmd == -1) {
                cmd = svc_EOF;
            }
        }

        if (cmd == svc_EOF) {
            SHOWNET(msg, "END OF MESSAGE");
            break;
        }

        if (cl_shownet->integer >= 2) {
            if ((cmd < 0) || (!svc_strings[cmd])) {
                Com_Printf("%3i:BAD CMD %i\n", msg->readcount - 1, cmd);
            } else {
                SHOWNET(msg, svc_strings[cmd]);
            }
        }

        // other commands
        switch (cmd) {
            default:
                Com_Error(ERR_DROP, "CL_ParseServerMessage: Illegible server message\n");
                break;
            case svc_nop:
                break;
            case svc_serverCommand:
                CL_ParseCommandString(msg);
                break;
            case svc_gamestate:
                CL_ParseGamestate(msg);
                break;
            case svc_snapshot:
                CL_ParseSnapshot(msg);
                break;
            case svc_download:
                CL_ParseDownload(msg);
                break;
            case svc_voip:
#ifdef USE_VOIP
                CL_ParseVoip(msg);
#endif
                break;
        }
    }
}


